/*
 * ObjetoTipoDatoReflexion.java
 *
 * Copyright 2011 Universidad Francisco de Paula Santander (UFPS).
 * Todos los Derechos Reservados.
 * Creado el 21/08/2010, 11:23:38 AM
 */

package com.ufps.cuatro.modelo.representacion;

import java.io.Serializable;
import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ufps.cuatro.util.Representacion;
import com.ufps.cuatro.util.TipoCaso;
import java.util.ArrayList;

/**
 * <i><b>ObjetoTipoDatoReflexion:</b></i> <p>Esta clase representa un tipo de dato
 * de un modelo analizado.</p> 
 * <p>El tipo de dato por lo general es representado por
 * su Class por lo tanto exige que esta abstracción sea representada
 * luego que el *.jar o modelo haya sido cargado en el path actual
 * de la maquina virtual de java.</p>
 * 
 * @author Carlos A. Torres C. - Dolly E. Guerrero. <https://code.google.com/p/tesis4dor/>
 * @version 1.0 21/08/2010
 * @see Observable
 * @see Serializable
 * @see Comparable
 */
public class ObjetoTipoDatoReflexion  extends Observable implements Serializable, Comparable{
    private static final long serialVersionUID = 3880582831535704234L;
    
    private String strClassPath;
    private String strNombre;
    private String strNamePropertie;
    private TipoCaso eCaso;
    private CasoRepresentacion objCaso;     
    private transient Class<?> objClass;
    private ArrayList<ObjetoTipoDatoReflexion> objTiposGenericos;
    private ObjetoTipoDatoReflexion objTipoIncluido;
    private boolean intoGenerico = false;
    private boolean coleccion = false;
    private boolean abstractClass = false;
    
    /**
     * Devuelve el Nombre del Tipo en Formato ClassPath, es decir en el formato 
     * de java.
     */
    public final static int NAME_CLASSPATH = 1;
    /**
     * Devuelve el Nombre del Tipo en Formato Componente, es decir en un formato
     * valido para representar Componentes.
     */
    public final static int NAME_COMPONENTE = 2;
    /**
     * Devuelve el Nombre del Tipo en Formato Array, es decir en el formato de
     * visualizacion de array en java.
     */
    public final static int NAME_ARRAY = 3;

    /**
     * Constructor por defecto de la Clase.
     */
    public ObjetoTipoDatoReflexion() {
        this.objTiposGenericos = new ArrayList<ObjetoTipoDatoReflexion>();
    }    

    /**
     * Constructor de la Clase.
     * @param objClass Class del Tipo de Dato a encapsular.
     */
    public ObjetoTipoDatoReflexion(Class<?> objClass) {
        this.objClass = objClass;
        this.strClassPath = objClass.getCanonicalName();        
        this.strNombre = objClass.getSimpleName();        
        this.objCaso = Representacion.crearCaso(objClass);
        this.objTiposGenericos = new ArrayList<ObjetoTipoDatoReflexion>();
    }

    public String getClassTypesGenericOnly(boolean mode) {
        String strTypesGeneric = "";
        for(int i = 0; i < this.objTiposGenericos.size(); i++){
            ObjetoTipoDatoReflexion objTipoDatoGenerico = this.objTiposGenericos.get(i);
            if(i + 1 == this.objTiposGenericos.size()){//fin
                if(mode){
                    strTypesGeneric += objTipoDatoGenerico.getClassPathCodeInto(this.getObjClass().getPackage().getName());
                }                    
                else{
                    strTypesGeneric += objTipoDatoGenerico.getClassPathCodeInto();
                }
            }
            else{
                if(mode){
                    strTypesGeneric += " " + objTipoDatoGenerico.getClassPathCodeInto(this.getObjClass().getPackage().getName()) + ",";                                        
                }
                else{
                    strTypesGeneric += " " + objTipoDatoGenerico.getClassPathCodeInto() + ",";                                        
                }
            }
        }
        return strTypesGeneric;
    }

    /**
     * Devuelve el Class del Tipo de dato encapsulado.
     * @return Class del Tipo de dato encapsulado.
     */
    public Class<?> getObjClass() {
        try {            
            if(this.objClass != null){
                return this.objClass;
            }
           if(strClassPath != null){
            Class<?> objClazz = Class.forName(strClassPath);
            return objClazz;}
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ObjetoTipoDatoReflexion.class.getName()).log(Level.SEVERE, null, ex);
        }catch (java.lang.NullPointerException ex) {
            Logger.getLogger(ObjetoTipoDatoReflexion.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    /**
     * Establece el Class del Tipo de dato encapsulado.
     * @param objClass Class del Tipo de dato encapsulado.
     */
    public void setObjClass(Class<?> objClass) {
        try{
            this.strClassPath = objClass.getCanonicalName();        
            this.strNombre = objClass.getSimpleName();
            this.setChanged();
            this.notifyObservers();
        }catch(java.lang.Exception ex){}
        
    }

    /**
     * Devuelve el Nombre Simple del Class del Tipo de dato encapsulado.
     * @return Nombre Simple del Class del Tipo de dato encapsulado.
     */
    public String getStrNombre() {
        return strNombre + this.getNamesTypesGeneric(ObjetoTipoDatoReflexion.NAME_CLASSPATH);
    }
    
    /**
     * Devuelve el Nombre Simple del Class del Tipo de dato encapsulado en un formato
     * determinado para su representación.
     * @param MODE Formato de Representación (Clase, Componente, Array).
     * @return Nombre Simple del Class del Tipo de dato encapsulado.
     */
    public String getStrNombre(int MODE) {
        return strNombre + this.getNamesTypesGeneric(MODE);
    }

    /**
     * Establece el Nombre Simple del Class del Tipo de dato encapsulado.
     * @param strNombre Nombre Simple del Class del Tipo de dato encapsulado.
     */
    public void setStrNombre(String strNombre) {
        this.strNombre = strNombre;
        this.setChanged();
        this.notifyObservers();
    }

    /**
     * Devuelve el Tipo del CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     * @return Tipo del CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     */
    public TipoCaso geteCaso() {
        return eCaso;
    }

    /**
     * Establece el Tipo del CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     * @param eCaso Tipo del CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     */
    public void seteCaso(TipoCaso eCaso) {
        this.eCaso = eCaso;
        this.setChanged();
        this.notifyObservers();
    }

    /**
     * Devuelve el CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     * @return CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     */
    public CasoRepresentacion getObjCaso() {
        return objCaso;
    }

    /**
     * Establece el CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     * @param objCaso CasoRepresentacion que representa el Class del Tipo de dato encapsulado.
     */
    public void setObjCaso(CasoRepresentacion objCaso) {
        try{
            this.objCaso = objCaso;
            this.eCaso = objCaso.geteCaso();
            this.setChanged();
            this.notifyObservers();
        }catch(java.lang.Exception ex){}    
    }

    /**
     * Devuelve el ClassPath del Tipo de Dato encapsulado.
     * @return ClassPath del Tipo de Dato encapsulado.
     */
    public String getStrClassPath() {
        return strClassPath;
    }

    /**
     * Establece el ClassPath del Tipo de Dato encapsulado.
     * @param strClassPath ClassPath del Tipo de Dato encapsulado.
     */
    public void setStrClassPath(String strClassPath) {
        this.strClassPath = strClassPath;
    }

    /**
     * Devuelve la Colección de Tipos Genéricos Aplicados al Tipo de Dato encapsulado.
     * @return Colección de Tipos Genéricos Aplicados al Tipo de Dato encapsulado.
     */
    public ArrayList<ObjetoTipoDatoReflexion> getObjTiposGenericos() {
        return objTiposGenericos;
    }

    /**
     * Devuelve la Referencia del Tipo Incluido en el Array.
     * @return Referencia del Tipo Incluido en el Array.
     */
    public ObjetoTipoDatoReflexion getObjTipoIncluido() {
        return objTipoIncluido;
    }

    /**
     * Establece la Referencia del Tipo Incluido en el Array.
     * @param objTipoIncluido Referencia del Tipo Incluido en el Array.
     */
    public void setObjTipoIncluido(ObjetoTipoDatoReflexion objTipoIncluido) {
        this.objTipoIncluido = objTipoIncluido;
    }    

    /**
     * Establece la Colección de Tipos Genéricos Aplicados al Tipo de Dato encapsulado.
     * @param objTiposGenericos Colección de Tipos Genéricos Aplicados al Tipo de Dato encapsulado.
     */
    public void setObjTiposGenericos(ArrayList<ObjetoTipoDatoReflexion> objTiposGenericos) {
        this.objTiposGenericos = objTiposGenericos;
    }
    
    private String getNamesTypesGeneric(int MODE){
        String strTypesGeneric = "";
        if(!this.objTiposGenericos.isEmpty()){
            
            switch(MODE){
                case ObjetoTipoDatoReflexion.NAME_CLASSPATH:
                    strTypesGeneric = "<";
                    break;
                case ObjetoTipoDatoReflexion.NAME_COMPONENTE:
                    strTypesGeneric = "";
                    break;
                case ObjetoTipoDatoReflexion.NAME_ARRAY:
                    strTypesGeneric = "{";
                    break;
            }
            
            for(int i = 0; i < this.objTiposGenericos.size(); i++){
                ObjetoTipoDatoReflexion objTipoDatoGenerico = this.objTiposGenericos.get(i);
                if(i + 1 == this.objTiposGenericos.size()){//fin
                    strTypesGeneric += objTipoDatoGenerico.strNombre;
                }
                else{
                    
                    switch(MODE){
                        case ObjetoTipoDatoReflexion.NAME_CLASSPATH:
                            strTypesGeneric += " " + objTipoDatoGenerico.strNombre + ",";
                            break;
                        case ObjetoTipoDatoReflexion.NAME_COMPONENTE:
                            strTypesGeneric += objTipoDatoGenerico.strNombre;
                            break;
                        case ObjetoTipoDatoReflexion.NAME_ARRAY:
                            strTypesGeneric += " " + objTipoDatoGenerico.strNombre + ",";
                            break;
                    }                 
                    
                }
            }
            
            switch(MODE){
                case ObjetoTipoDatoReflexion.NAME_CLASSPATH:
                    strTypesGeneric += ">";
                    break;
                case ObjetoTipoDatoReflexion.NAME_COMPONENTE:
                    strTypesGeneric += "";
                    break;
                case ObjetoTipoDatoReflexion.NAME_ARRAY:
                    strTypesGeneric += "}";
                    break;
            }            
        }
        return strTypesGeneric;
    }
    
    public String getClassTypesGeneric(boolean mode){
        String strTypesGeneric = "";
        if(!this.objTiposGenericos.isEmpty()){
            strTypesGeneric = "<";           
            strTypesGeneric += getClassTypesGenericOnly(mode);
            strTypesGeneric += ">";            
        }
        return strTypesGeneric;
    }

    /**
     * Devuelve el estado que indica que el Tipo de Dato encapsulado contiene genericos.
     * @return estado que indica que el Tipo de Dato encapsulado contiene genericos.
     */
    public boolean contieneRepresentacionesGenericas() {
        return intoGenerico;
    }

    /**
     * Establece el estado que indica que el Tipo de Dato encapsulado contiene genericos.
     * @param generico estado que indica que el Tipo de Dato encapsulado contiene genericos.
     */
    public void setContieneRepresentacionesGenericas(boolean generico) {
        this.intoGenerico = generico;
    }

    public boolean esColeccion() {
        return coleccion;
    }

    public void setColeccion(boolean coleccion) {
        this.coleccion = coleccion;
    }    

    @Override
    public String toString() {
        return strNombre;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof ObjetoTipoDatoReflexion){
            ObjetoTipoDatoReflexion objR = (ObjetoTipoDatoReflexion) obj;
            return this.getStrNombre().equals(objR.getStrNombre());
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 37 * hash + (this.strNombre != null ? this.strNombre.hashCode() : 0);
        return hash;
    }
    
    public String getClassPathCodeInto(){
        if(getObjClass() != null){
            if(this.getObjClass().getCanonicalName().startsWith("java.lang.")){
                //Excluir el Paquete Lang
                return this.getObjClass().getSimpleName();
            }                
            return this.getObjClass().getCanonicalName();
        }
        return null;
    }
    
    public String getClassPathCodeInto(boolean mode){
       if(getObjClass() != null){
            if(this.getObjClass().getCanonicalName().startsWith("java.lang.")){
                //Excluir el Paquete Lang
                return this.getObjClass().getSimpleName();
            }                
            return mode ? this.getObjClass().getCanonicalName(): this.getObjClass().getSimpleName();
        }
        return null;
    }
    
    public String getClassPathCodeInto(String strPackage){
       try{
            String strTempPackage = getClassPathCodeInto();
            if(this.getObjClass().getCanonicalName().startsWith(strTempPackage)){
                //Excluir el Paquete Local
                return this.getObjClass().getSimpleName();
            }
            return strTempPackage;
        }catch(java.lang.Exception ex){}
       return null;
    }
    
    

//    @Override
//    public Object clone() {
//        ObjetoTipoDatoReflexion objClon = null;
//        try {
//            //Primero se Clona la Referencia Local
//            objClon = (ObjetoTipoDatoReflexion) super.clone();
//        } catch (CloneNotSupportedException ex) {
//            Logger.getLogger(ObjetoTipoDatoReflexion.class.getName()).log(Level.SEVERE, null, ex);
//        }
//        //Ahora se hace la Clonación Profunda
//        if(objCaso != null){
//            ((ObjetoTipoDatoReflexion)objClon).setObjCaso((CasoRepresentacion)objCaso.clone());
//        }
//        if(objClass != null){
//            ((ObjetoTipoDatoReflexion)objClon).setObjClass(objClass);
//        }
//        if(objTipoIncluido != null){
//            ((ObjetoTipoDatoReflexion)objClon).setObjTipoIncluido((ObjetoTipoDatoReflexion)objTipoIncluido.clone());
//        }
//        if(objTiposGenericos != null){
//            ((ObjetoTipoDatoReflexion)objClon).setObjTiposGenericos((ArrayList<ObjetoTipoDatoReflexion>)objTiposGenericos.clone());
//        }
//        if(strClassPath != null){
//            ((ObjetoTipoDatoReflexion)objClon).setStrClassPath(new String(strClassPath.toCharArray()));
//        }
//        if(strNombre != null){
//            ((ObjetoTipoDatoReflexion)objClon).setStrNombre(new String(strNombre.toCharArray()));
//        }
//        return objClon;
//    }

    public boolean isAbstractClass() {
        return abstractClass;
    }

    public void setAbstractClass(boolean abstractClass) {
        this.abstractClass = abstractClass;
    }

    public int compareTo(Object o) {
        try{
        ObjetoTipoDatoReflexion obj = (ObjetoTipoDatoReflexion) o;
        int a = this.getObjCaso().geteCaso().ordinal();
        int b = obj.getObjCaso().geteCaso().ordinal();
        if(a > b) {
            return 1;
        }
        else if(a < b) {
            return -1;
        }
        else {//iguales decidir por medio de su class path 
            //verificar si existen genericos
            if(this.getObjCaso().geteCaso() == TipoCaso.eJDKRegistrado &&
                    this.esColeccion()){
                //verificar si existen genericos en ambos tipos
                if(this.contieneRepresentacionesGenericas() && obj.contieneRepresentacionesGenericas()){
                    //obtener sus genericos
                    String genericThis = this.getClassTypesGenericOnly(false);
                    String genericObj = obj.getClassTypesGenericOnly(false);
                    return genericThis.compareTo(genericObj);
                }
                else if(this.contieneRepresentacionesGenericas() && !obj.contieneRepresentacionesGenericas()){
                    return 1;
                }
                else if(!this.contieneRepresentacionesGenericas() && obj.contieneRepresentacionesGenericas()){
                    return -1;
                }
                return 0;
            }
            return this.strClassPath.compareTo(obj.strClassPath);
        }        
    }catch(java.lang.Exception ex){}
        return 0;
    }

    public boolean isIntoGenerico() {
        return intoGenerico;
    }

    public void setIntoGenerico(boolean intoGenerico) {
        this.intoGenerico = intoGenerico;
    }

    public String getStrNamePropertie() {
        return strNamePropertie;
    }

    public void setStrNamePropertie(String strNamePropertie) {
        this.strNamePropertie = strNamePropertie;
    }
}
